﻿using System;
using System.Configuration;
using System.Threading;
using System.Web;
using System.Web.Security;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;
using MVCms.Core.Entities;
using NHibernate;
using NHibernate.Linq;
using NHibernate.Tool.hbm2ddl;
using Configuration = NHibernate.Cfg.Configuration;

namespace MVCms.Core.DataAccess
{
    internal class McContext
    {
        #region Properties

        internal const string NHibernateSessionKey = "NHibernateSession";
        internal static readonly Configuration _configuration;
        internal static readonly ISessionFactory _sessionFactory;
        internal static readonly LocalDataStoreSlot _sessionDataSlot = Thread.AllocateDataSlot();

        #endregion

        /// <summary>
        /// Initializes the <see cref="McContext"/> class.
        /// </summary>
        static McContext()
        {

            log4net.Config.XmlConfigurator.Configure();

            _configuration = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2005.ConnectionString(ConfigurationManager.ConnectionStrings["MVCms"].ConnectionString).ShowSql())
                .Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<BaseEntity>().Where(t => t.Namespace == "MVCms.Core.Entities").OverrideAll(p => p.IgnoreProperties(x => x.GetCustomAttributes(typeof(IgnoreAttribute), false).Length > 0))
                .Conventions.Setup(c => c.Add<DefaultStringLengthConvention>())
                .Conventions.Add<CascadeAll>())/*.ExportTo(ConfigurationManager.AppSettings["EntityXML"])*/)
                .ExposeConfiguration(cfg => new SchemaExport(cfg).Create(false, bool.Parse(ConfigurationManager.AppSettings["RunMigrations"])))
                .BuildConfiguration();

            _configuration.SetInterceptor(new AuditInterceptor());
            _sessionFactory = _configuration.BuildSessionFactory();
        }

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        /// <value>The configuration.</value>
        internal static Configuration Configuration
        {
            get
            {
                return _configuration;
            }
        }

        /// <summary>
        /// Gets the session.
        /// </summary>
        /// <value>The session.</value>
        internal static ISession Session
        {
            get
            {
                ISession session;
                if (HttpContext.Current != null)
                {
                    session = HttpContext.Current.Items[NHibernateSessionKey] as ISession;
                    if (session == null)
                    {
                        lock (_sessionFactory)
                        {
                            session = HttpContext.Current.Items[NHibernateSessionKey] as ISession;
                            if (session == null)
                            {
                                session = _sessionFactory.OpenSession();
                                HttpContext.Current.Items[NHibernateSessionKey] = session;
                            }
                        }
                    }
                }
                else
                {
                    session = Thread.GetData(_sessionDataSlot) as ISession;
                    if (session == null)
                    {
                        lock (_sessionFactory)
                        {
                            session = Thread.GetData(_sessionDataSlot) as ISession;
                            if (session == null)
                            {
                                session = _sessionFactory.OpenSession();
                                Thread.SetData(_sessionDataSlot, session);
                            }
                        }
                    }
                }
                return session;
            }
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        internal static void SaveChanges()
        {
            Session.Flush();
        }

        /// <summary>
        /// Saves the specified obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal static void Save(object obj)
        {
            Session.SaveOrUpdate(obj);
        }

        /// <summary>
        /// Deletes the specified obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal static void Delete(object obj)
        {
            Session.Delete(obj);
        }

        /// <summary>
        /// Loads the specific object and allows for linq.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        internal static INHibernateQueryable<T> Load<T>()
        {
            return Session.Linq<T>();
        }

        #region Audit Interceptor

        class AuditInterceptor : EmptyInterceptor
        {
            public override bool OnFlushDirty(object Entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
            {
                var oEntity = Entity as BaseEntity;
                if ((oEntity != null))
                {
                    for (var i = 0; i < propertyNames.Length; i++)
                    {
                        if (propertyNames[i] == "UpdatedBy")
                        {
                            currentState[i] = HttpContext.Current.User.Identity.IsAuthenticated
                                                  ? Membership.GetUser(false).UserName
                                                  : "anonymous";
                        }

                        if (propertyNames[i] == "UpdatedOn")
                        {
                            currentState[i] = DateTime.Now;
                        }
                    }
                }
                return false;
            }

            public override bool OnSave(object Entity, object id, object[] currentState, string[] propertyNames, NHibernate.Type.IType[] types)
            {
                var oEntity = Entity as BaseEntity;
                if ((oEntity != null))
                {
                    for (var i = 0; i < propertyNames.Length; i++)
                    {
                        if (propertyNames[i] == "CreatedBy")
                        {
                            currentState[i] = HttpContext.Current.User.Identity.IsAuthenticated
                                                  ? Membership.GetUser(false).UserName
                                                  : "anonymous";
                        }

                        if (propertyNames[i] == "CreatedOn")
                        {
                            currentState[i] = DateTime.Now;
                        }

                        if (propertyNames[i] == "UpdatedBy")
                        {
                            currentState[i] = HttpContext.Current.User.Identity.IsAuthenticated
                                                  ? Membership.GetUser(false).UserName
                                                  : "anonymous";
                        }

                        if (propertyNames[i] == "UpdatedOn")
                        {
                            currentState[i] = DateTime.Now;
                        }
                    }
                }
                return false;
            }
        }

        #endregion

        #region Automapping Cascades

        private class CascadeAll : IHasOneConvention, IHasManyConvention, IReferenceConvention
        {
            public void Apply(IOneToOneInstance instance)
            {
                instance.Cascade.All();
            }

            public void Apply(IOneToManyCollectionInstance instance)
            {
                instance.Cascade.AllDeleteOrphan();
            }

            public void Apply(IManyToOneInstance instance)
            {
                //instance.Cascade.All();
            }
        }

        public class DefaultStringLengthConvention: IPropertyConvention
        {
            public void Apply(IPropertyInstance instance)
            {
                instance.Length(4001);
            }
        }

        #endregion

        public class IgnoreAttribute : Attribute { }

    }
}